#!/usr/bin/python
# -*- coding: utf-8 -*-

u"""
Base calsses for the game and states of the game.
"""
# TODO: document TheGame

__version__ = '$Id: __init__.py 142 2008-07-27 15:28:55Z DR0ID $'

from events import Event
from timing import GameTime
from timing import Scheduler




#------------------------------------------------------------------------------
# TODO: test and document new events event_any and event_unkown
class GameState(object):
    u"""
    This is ghe base for all states. It provides different events 
    (see events.Event).
    
    
    State events::
    
        self.event_update:          on_update(dt, t)
                                        called every frame with the time delta and the current time.
        self.event_init:            on_init(game_base, *args, **kwargs)
                                        called the first time the state is used
        self.event_exit:            on_exit()
                                        called once when the state is permanently removed
        self.event_activate_state   on_activate(game_base)
                                        calle if this state is woke up from state stack
        self.event_deactivate_state on_deactivate(game_base)
                                        called when another state has been pushed over this
        self.event_idle             on_idle()
                                        if no other events are in the queue this event is called
    
    Input events::
    
        self.event_quit              on_quit(game_state)
        self.event_close             same as self.event_quit
        self.event_video_expose      on_video_expose()
        self.event_expose            same as self.event_video_expose
        self.event_activate          on_activate()
        self.event_deactivate        on_deactivate()
        self.event_key_down          on_key_down(key, mod, unicode)
        self.event_key_up            on_key_up(key, mod)
        self.event_key_press         same as self.event_key_down
        self.event_key_release       same as self.event_key_up
        self.event_mouse_drag        on_mouse_drag(x, y, dx, dy, buttons)
        self.event_mouse_enter       on_mouse_enter(x, y)
        self.event_mouse_leave       on_mouse_leave(x, y)
        self.event_mouse_motion      on_mouse_motion(pos, rel)
        self.event_mouse_button_up   on_mouse_button_up(pos, button, mod)
        self.event_mouse_button_down on_mouse_button_down(pos, button, mod)
        self.event_video_resize      on_video_resize(w, h)
        self.event_resize            same as self.event_video_resize
    
    Default handlers:
        - self.on_close listens to the event_quit event
        - self.init listens to event_init, init state specific things
        - self.exit listens to event_exit, clean up state specific things
    
    Internal use for the state scheduler:
        - self._on_activate_scheduler listens to event_activate_state
        - self._on_deactivate_scheduler listens to event_deactivate_state
    
    Per default a state has:
        - game_time:
            converts dt and t to a game specific time (see timing.GameTime). 
            It defines its own event to register to.
    """

    def __init__(self, *args, **kwargs):
        u"""
        State init.
        """
        super(GameState, self).__init__(*args, **kwargs)
        #-- state events --#
        self.event_update = Event(u'event_update') # on_update(dt, t)
        self.event_init = Event(u'event_init') # on_init(game_base, *args, **kwargs)
        self.event_exit = Event(u'event_exit') # on_exit()
        self.event_activate_state = Event(u'event_activate_state') # on_activate(game_base)
        self.event_deactivate_state = Event(u'event_deactivate_state') # on_deactivate(game_base)
        self.event_idle = Event(u'event_idle') # on_idle()
        
        #-- input events --#
        #-- shared events --#
        self.event_quit = Event(u'event_quit')#               pygame.QUIT,           #  none
        self.event_video_expose = Event(u'event_video_expose or event_expose')#      pygame.VIDEOEXPOSE,    #  none
        self.event_expose = self.event_video_expose
        self.event_activate = Event(u'event_activate')   # The window was activated (got keyboard focus)
        self.event_deactivate = Event(u'event_deactivate') # The window was deactivated (lost keyboard focus)
        self.event_key_down = Event(u'event_key_down')#          pygame.KEYDOWN,        #  unicode, key, mod
        self.event_key_up = Event(u'event_key_up')#            pygame.KEYUP,          #  key, mod
        self.event_key_press = self.event_key_down
        self.event_key_release = self.event_key_up
        self.event_mouse_drag = Event(u'event_mouse_drag') #            x, y, dx, dy, buttons
        self.event_mouse_enter = Event(u'event_mouse_enter') # x, y
        self.event_mouse_leave = Event(u'event_mouse_leave') # x, y
        self.event_mouse_motion = Event(u'event_mouse_motion')#      pygame.MOUSEMOTION,    #  pos, rel
        self.event_mouse_button_up = Event(u'event_mouse_button_up')#    pygame.MOUSEBUTTONUP,  #  pos, button, mod
        self.event_mouse_button_down = Event(u'event_mouse_button_down')#  pygame.MOUSEBUTTONDOWN,#  pos, button, mod
        self.event_video_resize = Event(u'event_video_resize')#      pygame.VIDEORESIZE,    #  size, w, h
        self.event_resize = self.event_video_resize
        self.event_unknown = Event(u'event_unknown')
        self.event_close = self.event_quit
        #TODO: event_any ??
        #self.event_any = Event(u'event_any')

        #-- default handlers --#
        self.event_init += self.init
        self.event_exit += self.exit
        self.event_quit += self.on_close

        #-- timing --#
        self._game_time = GameTime()
        # register to the real time update event to fire the game time event
        self.event_update += self.game_time.update
        # TODO: add scheduler ?? Only one instance alouwed.
        # scheduler
#        self.scheduler = Scheduler()
#        self.event_activate += self.scheduler.on_activate
#        self.event_deactivate += self.scheduler.on_deactivate

        #-- focus --#
        # TODO: test and doc focus
        self._focus = None
        self.event_focus_changed = Event(u'event_focus_changed')

    def _set_focus(self, obj):
        self._focus = obj
        self.event_focus_changed(obj)

    focus = property(lambda self: self._focus, _set_focus, doc=u"get/set focus, setting fires event event_focus_changed")
    # TODO: test game_time property
    game_time = property(lambda self: self._game_time, doc=u"readonly, GameTime instance of this GameState")

    def on_close(self, game_base):
        u"""
        Default handler for event_quit (or event_close).
        Just calls game_base.quit() to exit game.
        """
        #print '<'*30
        game_base.quit()

    # TODO: rename to on_init and on_exit and on_render?
    def init(self, a_game_base, *args, **kwargs):
        u"""init the state and should call load"""
        pass

    def exit(self):
        u"""is called before the state is exited, should call unload"""
        pass

    def load(self):
        u"""all needed resources should be loaded here, you will have to
           call it manually (best from init)"""
        pass

    def unload(self):
        u"""all resources should be freed here, you will have to
           call it manually (best from exit)"""
        pass

    def render(self, a_delta_time):
        u"""renders the state"""
        pass

#------------------------------------------------------------------------------
TheGame = None

#------------------------------------------------------------------------------
class GameBase(object):
    u"""
    This is the base class for a game. It handles the states.
    """
    # action types
    _POP, _PUSH, _CHANGE = range(3)

    def __init__(self, first_game_state, state_init_args=[], state_init_kwargs={}, *args, **kwargs):
        u"""
        first_game_state is the first state of the game.
        state_init_args are the args for the state's init method
        state_init_kwargs are the kwargs for the state's init method
        #TODO: doc self._update(dt, t) here because it maps to _* funcs
        
        This class has a update update method: _update(dt, t)
        It is special because it init the engine the first time it is called, 
        then it will update the states in the consequent calls until quit is
        called. After the call to quit it will clean up the engine.
        
        _update(dt, t) will return True or False meaning if it is still running.
        It returns True only after the first call to it and before quit hass
        been called.
        During main iteration it updates the state in this order:
        
            - render(a_delta_time)
            - update(a_delta_time)
            - changes state
        
        a_delta_time should be the time since the last update call and t the 
        current time.
        
        This function is here for frameworks that have its own mainloop. The
        only thing you will have to do is call \_update(dt, t) and 
        _handle_events(\*args) from the other frameworks main loop.
        """
        super(GameBase, self).__init__()
        self._statechanges = [] # (action, (state, initargs, initkwargs))
        self._running = False
        #-- the starting state --#
        assert(isinstance(first_game_state, GameState))
        self._states = [GameState()] # dummy state will never be called
        self._statechanges.append((self._CHANGE, (first_game_state, state_init_args, state_init_kwargs)))
        self._cur_state = first_game_state #self._states[-1] # optimization
        self._update = self._first_update
        #-- fps handling --#
        self.event_framerate_changed = Event(u'event_framerate_changed')
        self.event_framerate_changed += self._on_framerate_changed
        self._tpf = 0 # tpf = 1/fps [ms]
        self.framerate = 33.333 # this gives 30 ms as time intervall per frame
        # make instance accessable
        global TheGame
        TheGame = self

    #-- properties --#
    def _set_framerate(self, fps):
        if fps > 0:
            self._tpf = 1000.0 / fps
        elif fps <= 0:
            self._tpf = -1
        self.event_framerate_changed(self._tpf)
    framerate = property(lambda self: 1000.0 / self._tpf if self._tpf > 0 else 0, 
                         _set_framerate, u"get/set the desired framerate \
                         [frames per second], if set to 0 or negative it will\
                         react on events only and you are responsible to call\
                             _update(dt, t) periodically to update the state")

    # TODO: read only property fps which should return the actual fps

    #-- --#

    def init(self):
        u"""
        init the game data, automatically called the first time update is
        called.
        """
        pass

    def exit(self):
        u"""
        exit is called automatically from update, when quit() has been called. 
        It gives the chance to clean up.
        """
        pass

    def _on_framerate_changed(self, tpf):
        u"""
        Should be overwritten. Adapt fps in the framework.
        """
        pass

    #-- scheduler hooks --#

    def _schedule_once(self, func, dt):
        u"""
        Should be overwritten. Schedule this func to the framework's timer.
        """
        raise NotImplementedError()
        pass

    def _unschedule(self, func):
        u"""
        Should be overwritten. Remove func from eht framework's timer.
        """
        raise NotImplementedError()
        pass

    def _get_time(self):
        u"""
        Should be overwritten. Return (real) time since start [ms].
        """
        raise NotImplementedError()
        pass

    #-- event handling --#

    def _handle_events(self, *args, **kwargs):
        u"""
        Should be overwritten. Translate into GameState events.
        """
        pass

    #-- run methods --#

    def run(self, callback_update=None, fargs=[], fkwargs={}):
        u"""
        Should be overwritten.
        
        It will call self.update(dt, t) until quit() is called.
        
        You can provide your own timing function that should return the time 
        passed since last update: callback_update(\*fargs, \*\*kwargs) -> dt
        
        if no callback_update is passed, then a dummy callback is used 
        always returning 1 as dt
        """
        # check if callback_update has to be set to default
        if callback_update is None:
            callback_update = lambda: 1
        self._update = self._first_update
        dt = time = 0
        # start main loop
        while self._update(dt, time):
            # call back (get time delta)
            dt = callback_update(*fargs, **fkwargs)
            # update
            time += dt
            self._handle_events()

    def quit(self):
        u"""
        call this to stop the game from running
        """
        self._update = self._last_update

    #-- mainloop methods --#

    def _first_update(self, dt, t):
        u"""
        First call to update will execute this function. Internal use only.
        """
        self._running = True
        self.init()
        self._change_states()
        #print '\n', '>'*10, 'init_first'
        self._update = self._loop_update
        return self._update(dt, t)

    def _last_update(self, dt, t):
        u"""
        Last call to update will execute this function . Internal use only.
        """
        self._running = False
        while self._states:
            state = self._states.pop(-1)
            state.event_deactivate_state(self)
            state.event_exit()
        self.exit()
        self._update = lambda dt, t: False
        #print '\n', '>'*10, 'last_update'
        return False

    def _loop_update(self, a_delta_time, a_time):
        """
        Main iteration update function. See documentation of _update(dt, t).
        Internal use only.
        """
        # render
        self._cur_state.render(a_delta_time)
        # update the logic
        self._cur_state.event_update(a_delta_time, a_time)
        # update statechanges
        if self._statechanges :
            self._change_states()
        return True

    def _change_states(self):
        u"""
        Changes the state accordingly to what it is in the statechange queue.
        Internal use only.
        """
        while(self._statechanges and self._running):
            action, args = self._statechanges.pop()
            if self._POP == action:
                if len(self._states) > 1:
                    state = self._states.pop()
                    state.event_deactivate_state(self)
                    state.event_exit()
                    self._states[-1].event_activate_state(self)
                else:
                    self._update = self._last_update # clean up
            elif self._PUSH == action:
                self._states[-1].event_deactivate_state(self)
                state, args, kwargs = args
                state.event_init(self, *args, **kwargs)
                state.event_activate_state(self)
                self._states.append(state)
            elif self._CHANGE == action:
                old_state = self._states.pop()
                old_state.event_deactivate_state(self)
                old_state.event_exit()
                state, args, kwargs = args
                state.event_init(self, *args, **kwargs)
                state.event_activate_state(self)
                self._states.append(state)
        self._cur_state = self._states[-1] # optimization

    #-- state handling --#

    def get_game_state(self):
        u"""
        returns the current gamestate or None.
        """
        return self._cur_state

    def set_game_state(self, a_game_state, *initargs, **initkwargs):
        u"""
        Exchanges the current state with this state. The actual change occures
        in the next update (deferred). The current state will terminated 
        (calling exit) and be replaced by the new state.
        """
        # buffer new state (so a state can set a new state 
        # and be shut down properly)
        assert(isinstance(a_game_state, GameState))
        self._statechanges.insert(0, (self._CHANGE, (a_game_state, initargs, initkwargs)))

    def push(self, a_game_state, *initargs, **initkwargs):
        u"""
        Push a gamestate on top of the existing one, pausing the current one. 
        The actual change occures in the next update (deferred).
        """
        assert(isinstance(a_game_state, GameState))
        self._statechanges.insert(0, (self._PUSH, (a_game_state, initargs, initkwargs)))

    def pop(self):
        u"""
        Pop the current state from the state stack. The actual change occures
        in the next update (deferred). If no state is left on the statestack
        it will exit the game (same way as calling quit() ).
        """
        self._statechanges.insert(0, (self._POP, tuple()))

    #TODO: implement

    def switch_state(self, other_state):
        raise NotImplementedError

#------------------------------------------------------------------------------


